/*******************************************************************************
 * Copyright (c) 2004, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.draw2d.text;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.draw2d.geometry.Rectangle;

/**
 * LineRoot is the top-most container on a line of text displayed in Draw2d.  Hence, a
 * LineRoot can tell you of things like the highest ascent or descent on a line, which
 * is required to display selection and such.  All 
 * {@link org.eclipse.draw2d.text.ContentBox fragments} know of the LineRoot they belong 
 * to.
 * @author Randy Hudson
 * @author Pratik Shah
 * @since 3.1
 */
public class LineRoot
	extends LineBox 
{

private int baseline;
private boolean isMirrored;

/**
 * Constructor
 * @param isMirrored <code>true</code> if the line is to be displayed in a mirrored control
 */
public LineRoot(boolean isMirrored) {
	this.isMirrored = isMirrored;
}

/**
 * @see org.eclipse.draw2d.text.CompositeBox#add(org.eclipse.draw2d.text.FlowBox)
 */
public void add(FlowBox child) {
	super.add(child);
	child.setLineRoot(this);
}

private void bidiCommit() {
	int xLocation = getX();
	BidiLevelNode root = new BidiLevelNode();
	List branches = new ArrayList();
	// branches does not include this LineRoot; all the non-leaf child fragments of a 
	// parent will be listed before the parent itself in this list
	buildBidiTree(this, root, branches);
	List result = new ArrayList();
	root.emit(result);
	int i = isMirrored ? result.size() - 1 : 0;
	while (i >= 0 && i < result.size()) {
		FlowBox box = (FlowBox)result.get(i);
		box.setX(xLocation);
		xLocation += box.getWidth();
		i += isMirrored ? -1 : 1;
	}
	// set the bounds of the composite boxes, and break overlapping ones into two
	layoutNestedLines(branches);
}

private void buildBidiTree(FlowBox box, BidiLevelNode node, List branches) {
	if (box instanceof LineBox) {
		List children = ((LineBox)box).getFragments();
		for (int i = 0; i < children.size(); i++)
			buildBidiTree((FlowBox)children.get(i), node, branches);
		if (box != this)
			branches.add(box);
	} else {
		ContentBox leafBox = (ContentBox)box;
		while (leafBox.getBidiLevel() < node.level)
			node = node.pop();
		while (leafBox.getBidiLevel() > node.level)
			node = node.push();
		node.add(leafBox);
	}
}

/**
 * Committing a LineRoot will position its children correctly. All children boxes are made
 * to have the same baseline, and are laid out according to the Unicode BiDi Algorithm,
 * or left-to-right if Bidi is not necessary.
 */
public void commit() {
	if (requiresBidi())
		bidiCommit();
	else
		contiguousCommit(this, getX());
}

/**
 * A LineRoot cannot be targetted.
 * @see org.eclipse.draw2d.text.FlowBox#containsPoint(int, int)
 */
public boolean containsPoint(int x, int y) {
	return false;
}

/*
 * Simply lays out all fragments from left-to-right in the order in which they're 
 * contained. 
 */
private void contiguousCommit(FlowBox box, int x) {
	box.setX(x);
	if (box instanceof LineBox) {
		List fragments = ((LineBox)box).getFragments();
		int i = isMirrored ? fragments.size() - 1 : 0;		
		while (i >= 0 && i < fragments.size()) {
			FlowBox child = (FlowBox)fragments.get(i);
			contiguousCommit(child, x);
			x += child.getWidth();
			i += isMirrored ? -1 : 1;
		}
	}
}

private Result findParent(NestedLine line, List branches, int afterIndex) {
	for (int i = afterIndex + 1; i < branches.size(); i++) {
		NestedLine box = (NestedLine)branches.get(i);
		int index = box.getFragments().indexOf(line);
		if (index >= 0)
			return new Result(box, index);
	}
	return new Result(this, getFragments().indexOf(line));
}

/**
 * @see org.eclipse.draw2d.text.FlowBox#getBaseline()
 */
public int getBaseline() {
	return baseline;
}

LineRoot getLineRoot() {
	return this;
}

int getVisibleBottom() {
	return baseline + contentDescent;
}

int getVisibleTop() {
	return baseline - contentAscent;
}

private void layoutNestedLines(List branches) {
	for (int i = 0; i < branches.size(); i++) {
		NestedLine parent = (NestedLine)branches.get(i);
		FlowBox prevChild = null;
		Rectangle bounds = null;
		List frags = parent.getFragments();
		for (int j = 0; j < frags.size(); j++) {
			FlowBox child = (FlowBox) frags.get(j);
			if (prevChild != null && prevChild.getX() + prevChild.width != child.getX()
					&& child.getX() + child.width != prevChild.getX()) {
				// the boxes are not adjacent, and hence the parent box needs to
				// be broken up
				InlineFlow parentFig = parent.owner;
				// Create and initialize a new line box
				NestedLine newBox = new NestedLine(parentFig);
				newBox.setLineRoot(this);
				// Add all remaining fragments from the current line box to the new one
				for (int k = j; k < frags.size();)
					newBox.fragments.add(frags.remove(k));
				// Add the new line box to the parent box's list of fragments
				Result result = findParent(parent, branches, i);
				result.parent.getFragments().add(result.index + 1, newBox);
				// Add the new line box to the flow figure's list of fragments 
				parentFig.fragments.add(parentFig.fragments.indexOf(parent) + 1, newBox);
				branches.add(i + 1, newBox);
				break;
			}
			if (bounds == null)
				bounds = new Rectangle(child.getX(), 1, child.getWidth(), 1);
			else
				bounds.union(child.getX(), 1, child.getWidth(), 1);
			prevChild = child;
		}
		parent.setX(bounds.x);
		parent.setWidth(bounds.width);
	}
}

/**
 * Positions the line vertically by settings its baseline.
 * @param baseline the baseline
 */
public void setBaseline(int baseline) {
	this.baseline = baseline;
}

/**
 * @see org.eclipse.draw2d.text.CompositeBox#setLineTop(int)
 */
public void setLineTop(int top) {
	this.baseline = top + getAscent();
}

private static class BidiLevelNode extends ArrayList
{
	int level;
	final BidiLevelNode parent;

	BidiLevelNode() {
		this(null, 0);
	}

	BidiLevelNode(BidiLevelNode parent, int level) {
		this.parent = parent;
		this.level = level;
	}

	void emit(List list) {
		if (level % 2 == 1) {
			for (int i = size() - 1; i >= 0; i--) {
				Object child = get(i);
				if (child instanceof BidiLevelNode)
					((BidiLevelNode)child).emit(list);
				else
					list.add(child);
			}
		} else {
			for (int i = 0; i < size(); i++) {
				Object child = get(i);
				if (child instanceof BidiLevelNode)
					((BidiLevelNode)child).emit(list);
				else
					list.add(child);
			}
		}
	}

	BidiLevelNode pop() {
		return parent;
	}

	BidiLevelNode push() {
		if (!isEmpty()) {
			Object last = get(size() - 1);
			if (last instanceof BidiLevelNode && ((BidiLevelNode)last).level == level + 1)
				return (BidiLevelNode)last;
		}
		BidiLevelNode child = new BidiLevelNode(this, level + 1);
		add(child);
		return child;
	}
}

private static class Result {
	private int index;
	private LineBox parent;
	private Result(LineBox box, int i) {
		parent = box;
		index = i;
	}
}

}